package edu.udel.mboch.towerdefense;

import java.io.Serializable;
import java.util.ArrayList;

public abstract class Grid implements Serializable
{
	private static final long serialVersionUID = 1L;
	public static final int MAX_X = 20;
	public static final int MAX_Y = 20;
	
	protected Tower[][] towerGrid;
	protected Mob[][] mobGrid;
	protected Terrain[][] terrainGrid;
	protected Position spawn;
	protected ArrayList<Mob> mobs;
	protected ArrayList<Projectile> projectiles;
	protected Position end;
	protected ArrayList<PathPosition> path;
	
	public Grid(Position spawn, Position end, Terrain[][] terrainGrid)
	{
		this.terrainGrid = terrainGrid;
		towerGrid =  new Tower[MAX_Y][MAX_X];
		mobGrid = new Mob[MAX_Y][MAX_X];
		this.spawn = spawn;
		this.end = end;
		this.path = findPathPoints();
		projectiles = new ArrayList<Projectile>();
		mobs = new ArrayList<Mob>();
	}
	
	public Grid(Tower[][] towerGrid, Mob[][] mobGrid, Terrain[][] terrainGrid, Position spawn, Position end, ArrayList<PathPosition> path, ArrayList<Mob> mobs, ArrayList<Projectile> projectiles)
	{
		this.terrainGrid = terrainGrid;
		this.towerGrid = towerGrid;
		this.mobGrid = mobGrid;
		this.spawn = spawn;
		this.end = end;
		this.mobs = mobs;
		this.projectiles = projectiles;
	}
	
	private ArrayList<PathPosition> findPathPoints()
	{
		ArrayList<PathPosition> allPos = new ArrayList<PathPosition>();
		allPos.add(new PathPosition(end.x(), end.y(), 0));
		int count = 1;
		int i = 0;
		PathPosition currentPos = allPos.get(0);
		while(currentPos.x() != spawn.x() || currentPos.y() != spawn.y())
		{
			ArrayList<PathPosition> newPoses = new ArrayList<PathPosition>();
			int x = currentPos.x();
			int y = currentPos.y();
			if(x+1 < MAX_X && !terrainGrid[y][x+1].isBuildable())
				newPoses.add(new PathPosition(x+1, y, count));
			if(x-1 >= 0 && !terrainGrid[y][x-1].isBuildable())
				newPoses.add(new PathPosition(x-1, y, count));
			if(y+1 < MAX_Y && !terrainGrid[y+1][x].isBuildable())
				newPoses.add(new PathPosition(x, y+1, count));
			if(y-1 >= 0 && !terrainGrid[y-1][x].isBuildable())
				newPoses.add(new PathPosition(x, y-1, count));
			count++;
			ArrayList<PathPosition> newPosesCopy = new ArrayList<PathPosition>();
			for(PathPosition p1 : newPoses)
			{
				newPosesCopy.add(p1);
				for(PathPosition p2 : allPos)
					if(p1.x() == p2.x() && p1.y() == p2.y() && p2.getCount() <= p1.getCount())
						newPosesCopy.remove(p1);
			}
			newPoses = newPosesCopy;
			for(PathPosition p : newPoses)
				allPos.add(p);
			currentPos = allPos.get(++i);
		}
		
		ArrayList<PathPosition> path = new ArrayList<PathPosition>();
		path.add(new PathPosition(spawn.x(), spawn.y(), count));
		currentPos = path.get(0);
		while(!currentPos.equals(allPos.get(0)))
		{
			PathPosition nextPos = new PathPosition(currentPos.x(), currentPos.y(), currentPos.getCount());
			for(PathPosition p : allPos)
			{
				if(currentPos.isNextTo(p) && p.getCount() < nextPos.getCount())
					nextPos = p;	
			}
			currentPos = nextPos;
			path.add(currentPos);
		}
		path.remove(0);
		return path;
	}
	
	public ArrayList<PathPosition> getPath()
	{
		return path;
	}
	
	public Tower[][] getTowers()
	{
		return towerGrid;
	}
	
	public Mob[][] getMobGrid()
	{
		return mobGrid;
	}
	
	public ArrayList<Mob> getMobs()
	{
		return mobs;
	}
	
	public ArrayList<Projectile> getProjectiles()
	{
		return projectiles;
	}
	
	public Terrain[][] getTerrain()
	{
		return terrainGrid;
	}
	
	public Position getEnd()
	{
		return end;
	}
	
	public Position getSpawn()
	{
		return spawn;
	}
	
	public void buildTower(Tower tower)
	{
		int x = tower.getPosition().x();
		int y = tower.getPosition().y();
		if(towerGrid[y][x] == null && terrainGrid[y][x].isBuildable())
		{
			towerGrid[y][x] = tower;
		}
	}
	
	public void removeTower(Tower tower)
	{
		towerGrid[tower.getPosition().y()][tower.getPosition().x()] = null;
	}
	
	public boolean isValid(Tower tower)
	{
		int x = tower.getPosition().x();
		int y = tower.getPosition().y();
		if(x >= MAX_X || x < 0 || y >= MAX_Y || y < 0)
			return false;
		if(towerGrid[y][x] == null && terrainGrid[y][x].isBuildable())
			return true;
		return false;
	}
	
	public boolean isValid(Mob mob)
	{
		int x = mob.getPosition().toPosition().x();
		int y = mob.getPosition().toPosition().y();
		if(x >= MAX_X || x < 0 || y >= MAX_Y || y < 0)
			return false;
		if(mobGrid[y][x] == null && !terrainGrid[y][x].isBuildable())
			return true;
		return false;
	}
	
	public void removeMob(Mob mob)
	{
		mobGrid[mob.getPosition().toPosition().y()][mob.getPosition().toPosition().x()] = null;
		mobs.remove(mob);
	}
	
	public void placeMob(Mob mob)
	{
		mobGrid[mob.getPosition().toPosition().y()][mob.getPosition().toPosition().x()] = mob;
		mobs.add(mob);
	}
	
	public boolean mobIsOnGrid(Mob mob)
	{
		return mobs.contains(mob);
	}
	
	public void updateMob(Position oldPos, Mob mob)
	{
		mobGrid[oldPos.y()][oldPos.x()] = null;
		mobGrid[mob.getPosition().toPosition().y()][mob.getPosition().toPosition().x()] = mob;
	}
	
	public void advanceMobs(long time, TDState state)
	{
		Mob[] mobsCopy = new Mob[mobs.size()];
		for(int i = 0; i < mobsCopy.length; i++)
			mobsCopy[i] = mobs.get(i);
		for(Mob mob : mobsCopy)
		{
			mob.addTime(time);
			mob.advanceMob(state);
		}
	}
	
	public void updateProjectiles(long time, TDState state)
	{
		Projectile[] projectilesCopy = new Projectile[projectiles.size()];
		for(int i = 0; i < projectilesCopy.length; i++)
			projectilesCopy[i] = projectiles.get(i);
		for(Projectile p : projectilesCopy)
		{
			p.addTime(time);
			p.advanceProjectile(state);
		}
	}
	
	public void addProjectile(Projectile projectile)
	{
		projectiles.add(projectile);
	}
	
	public void removeProjectile(Projectile projectile)
	{
		projectiles.remove(projectile);
	}
	
	public void shootTowers(long time, TDState state)
	{
		for(Tower[] towers : towerGrid)
			for(Tower t : towers)
				if(t != null)
				{
					t.addTime(time);
					t.shoot(state);
				}
	}
	
	public void print()
	{
		for(int i = 0; i<MAX_Y; i++)
		{
			for(int j = 0; j<MAX_X; j++)
			{
				if(mobGrid[i][j] != null)
					mobGrid[i][j].print();
				else if(towerGrid[i][j] != null)
					towerGrid[i][j].print();
				else
					terrainGrid[i][j].print();
			}
			System.out.println();
		}
	}
	
	public void printTowers()
	{
		for(Tower[] towers : towerGrid)
			for(Tower t : towers)
			{
				if(t!=null)
				{
					System.out.println(t.getPosition());
					for(Position p : t.getPositionsInRange())
						System.out.println(p);
				}
			}
	}
	
	public void printMobs()
	{
		for(Mob[] mobs : mobGrid)
			for(Mob m : mobs)
				if(m!=null)
					System.out.println(m);
	}
	
	public abstract Grid copy();
}
